#pragma once

#include "CachePolicy.h"

#include <iostream>
#include <memory>
#include <mutex>
#include <unordered_map>

namespace Cache {

template <typename Key, typename Value>
class FifoCache;

template <typename Key, typename Value>
class FifoNode {
  private:
    Key key_;
    Value val_;
    std::shared_ptr<FifoNode> next_;  //! next_ = nullptr, next_(nullptr)
    std::weak_ptr<FifoNode> prev_;    //! weak_ptr 可以有默认构造函数，prev_(), 不可以 prev_(nullptr)

  public:
    FifoNode() = default;
    FifoNode(Key key, Value val) : key_(key), val_(val), next_(nullptr), prev_() {}
    Value getValue() const { return val_; }
    void setValue(const Value &val) { val_ = val; }

    friend class FifoCache<Key, Value>;
};

template <typename Key, typename Value>
class FifoCache final : public CachePolicy<Key, Value> {
  private:
    using FifoNodeType = FifoNode<Key, Value>;
    using NodePtr = std::shared_ptr<FifoNodeType>;
    using NodeMap = std::unordered_map<Key, NodePtr>;

  public:
    explicit FifoCache(const int capacity) : capacity_(capacity) {
        dHead_ = std::make_shared<FifoNodeType>(Key(), Value());  // 1
        dTail_ = std::make_shared<FifoNodeType>(Key(), Value());  // 2
        dHead_->next_ = dTail_;
        dTail_->prev_ = dHead_;
    }

    ~FifoCache() override = default;

    void put(Key key, Value val) override {
        if (capacity_ <= 0)
            return;

        std::lock_guard<std::mutex> lock(mtx_);
        if (capacity_ == nodeMap_.size()) {
            auto node = dHead_->next_;
            dHead_->next_ = node->next_;
            node->next_->prev_ = dHead_;
            node = nullptr;

            nodeMap_.erase(node->key_);
        }

        NodePtr node = std::make_shared<FifoNodeType>(key, val);

        nodeMap_[key] = node;

        node->next_ = dTail_;
        node->prev_ = dTail_->prev_;
        dTail_->prev_ = node;
        node->prev_.lock()->next_ = node;
    }

    bool get(Key key, Value &val) override {
        std::lock_guard<std::mutex> lock(mtx_);
        if (nodeMap_.contains(key)) {
            val = nodeMap_[key]->val_;
            return true;
        }
        return false;
    }

    Value get(Key key) override {
        Value val{};
        get(key, val);
        return val;
    }

  private:
    int capacity_{0};
    NodePtr dHead_;
    NodePtr dTail_;
    std::mutex mtx_;
    NodeMap nodeMap_;
};

}  // namespace Cache